package com.lipaluma.field.mapper.single;

import java.lang.reflect.Field;

import com.lipaluma.context.Context;
import com.lipaluma.field.mapper.AbstractFieldMapper;
import com.lipaluma.field.mapper.FieldAdapted;
import com.lipaluma.field.mapper.FieldMapper;
import com.lipaluma.field.mapper.FieldValidated;
import com.lipaluma.field.mapper.initializer.FieldMapperInitializer;
import com.lipaluma.field.mapper.initializer.XmlFieldMapperInitializer;
import com.lipaluma.field.mapper.mapping.MappingInfo;
import com.lipaluma.field.mapper.mapping.MappingOptions;
import com.lipaluma.io.Row;
import com.lipaluma.parser.marshalling.ObjectMarshalled;
import com.lipaluma.xml.mapping.XmlFieldMapping;
/**
 * Field Mapper used to map a single column of the external source.<br/>
 * 
 * <p>
 * This abstract class defines a default algorithm to marshall and unmarshall data :
 * <p>
 * <b>The unmarshall algorithm</b> : 
 * <ul>
 * 	<li>extract the value of the mapped column from the external source</li>
 * 	<li>only if the subclass that extends {@link AbstractSingleFieldMapper} implements {@link FieldAdapted} : adapt the value extracted</li>
 * 	<li>parse the original/adapted value to the type of the field</li>
 * 	<li>only if the subclass that extends {@link AbstractSingleFieldMapper} implements {@link FieldValidated} : validate the value parsed</li>
 * 	<li>inject the value parsed into the target java object</li>
 * </ul>
 * <p>
 * <b>The marshall algorithm</b> : 
 * <ul>
 * 	<li>extract the value from the field mapped of the java Object</li>
 * 	<li>only if the subclass that extends {@link AbstractSingleFieldMapper} implements {@link FieldValidated} : validate the value</li>
 * 	<li>format the value to be write on external source</li>
 * 	<li>write the value to the external source</li>
 * </ul>
 * @author Mario
 */
public abstract class AbstractSingleFieldMapper extends AbstractFieldMapper implements FieldMapperInitializer, XmlFieldMapperInitializer {
	protected MappingInfo mapping;
	
	public AbstractSingleFieldMapper() {}
	/**
	 * Initialize a {@link FieldMapper} with the mapping info of the source
	 * @param mapping information of mapping
	 */
	public AbstractSingleFieldMapper(MappingInfo mapping) {
		this.mapping = mapping;
	}

	@Override
	public void init(Context context, Field field) {
		this.field = field;
		if(context != null) {
			if(this.mapping == null)
				this.mapping = context.getBasicMappingManager().initMapping(field);
			MappingOptions options = context.getBasicMappingManager().initOptions(field);
			if(options != null)
				init(options);
			if(FieldAdapted.class.isAssignableFrom(this.getClass()))
				((FieldAdapted)this).initAdaptor(context, field);
			if(FieldValidated.class.isAssignableFrom(this.getClass()))
				((FieldValidated)this).initValidator(context, field);
		}
	}
	
	@Override
	public void init(Context context, Field field, XmlFieldMapping mapping) {
		this.field = field;
		this.mapping = context.getMappingManagerByXml().initMapping(field, mapping);
		init(MappingOptions.builder().by(mapping).build());
		if(FieldAdapted.class.isAssignableFrom(this.getClass()))
			((FieldAdapted)this).initAdaptor(context, field, mapping);
		if(FieldValidated.class.isAssignableFrom(this.getClass()))
			((FieldValidated)this).initValidator(context, field, mapping);
	}
	
	protected abstract void init(MappingOptions mappingOptions);
		
	/**
	 * Extract the value with this algorithm :
	 * <ul>
	 * <li>Extract the value from the row of the external source of data</li>
	 * <li>Adapt the value (Only if subclass implements {@link FieldAdapted})</li>
	 * <li>Parse the value to the type of the field</li>
	 * <li>Validate the value parsed (only if subclass implements {@link FieldValidated})</li>
	 * <li>Returns the value parsed if the value is valid. Throws an exception, else</li>
	 * </ul>
	 */
	@Override
	public Object unmarshal(Row row) {
		String value = row.extractValue(mapping);
		if(FieldAdapted.class.isAssignableFrom(this.getClass()))
			value = ((FieldAdapted)this).adaptValue(value);
		Object valueParsed = null;
		if(value != null)
			valueParsed = parseExtractedValue(value);
		if(FieldValidated.class.isAssignableFrom(this.getClass()))
			((FieldValidated)this).validateValue(valueParsed);
		return valueParsed;
	}
	
	/**
	 * Parse the extracted value to the type of the object ready to be injected
	 * @param value value extracted from the source
	 * @return the value parsed ready to be injected
	 */
	protected abstract Object parseExtractedValue(String value);
	
	/**
	 * Write the value with this algorithm
	 * <ul>
	 * <li>Validate the value (only if subclass implements {@link FieldValidated})</li>
	 * <li>If the value is valid, format the value to be write. Throws an Exception, else</li>
	 * <li>Write the value on the external source</li>
	 * </ul>
	 */
	@Override
	public void marshalValue(Object value, ObjectMarshalled row) {
		if(FieldValidated.class.isAssignableFrom(this.getClass()))
			((FieldValidated)this).validateValue(value);
		if(value != null) {
			Object valueFormatted = formatValueToWriteOnRow(value);
			row.addValueMarshalledOnMapping(mapping, valueFormatted);
		}
	}

	/**
	 * format the value before inserting in the row
	 * @param value the value to format
	 * @return the value formatted
	 */
	protected abstract Object formatValueToWriteOnRow(Object value);
	
	
	public String getDescription() {
		StringBuilder description = new StringBuilder("MapColumn description : \n");
		description.append("     - column mapper class : ").append(this.getClass()).append("\n");
		if(field != null)
			description.append("     - field : ").append(field.getName()).append(" of type ").append(field.getType()).append("\n");
		if(mapping != null)
			description.append("     - mapping : ").append(mapping).append("\n");
		return description.toString();
	}
	
	@Override
	public MappingInfo[] getMappings() {
		return new MappingInfo[]{mapping};
	}
}
